#!/usr/bin/env python3
"""
GitLab仓库提交统计分析工具
获取仓库提交记录并进行可视化分析
"""

import os
import random
import time
from datetime import datetime
from typing import Dict, List, Optional
from urllib.parse import quote_plus

import matplotlib.pyplot as plt
import pandas as pd
import requests
from loguru import logger

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False


class GitLabCommitsAnalyzer:
    """GitLab提交记录分析器"""

    def __init__(self, access_token: str, gitlab_url: str = "http://gitlab.waterism.com:8888"):
        """
        初始化GitLab提交分析器
        
        Args:
            access_token: GitLab访问令牌
            gitlab_url: GitLab实例URL
        """
        self.access_token = access_token
        self.gitlab_url = gitlab_url.rstrip('/')
        self.base_url = f"{self.gitlab_url}/api/v4"
        self.session = requests.Session()

        # 设置请求头
        self.session.headers.update({
            'User-Agent': 'GitLab-Python-Client/1.0',
            'Accept': 'application/json',
            'Content-Type': 'application/json',
            'PRIVATE-TOKEN': self.access_token
        })

    def get_repo_commits(self, project_id: str, **kwargs) -> List[Dict]:
        """
        获取项目的提交记录
        
        Args:
            project_id: 项目ID或路径
            **kwargs: 其他查询参数
                - ref_name: 分支名称 (默认为默认分支)
                - since: 开始时间 (ISO 8601格式)
                - until: 结束时间 (ISO 8601格式)
                - path: 文件路径过滤
                - author: 作者过滤
                - per_page: 每页数量 (1-100, 默认20)
                - page: 页码 (默认1)
        
        Returns:
            提交记录列表
        """
        # 对项目ID进行URL编码，支持路径格式的项目ID
        encoded_project_id = quote_plus(str(project_id))
        url = f"{self.base_url}/projects/{encoded_project_id}/repository/commits"

        # 构建查询参数
        params = {}

        # 添加可选参数
        optional_params = ['ref_name', 'since', 'until', 'path', 'author', 'per_page', 'page']
        for param in optional_params:
            if param in kwargs and kwargs[param]:
                params[param] = kwargs[param]

        logger.info(f"GitLab API请求: {url}")
        logger.info(f"请求参数: {params}")
        logger.info(f"项目ID: {project_id}")

        # 首先检查项目是否为空仓库
        try:
            project_info_url = f"{self.base_url}/projects/{encoded_project_id}"
            project_response = self.session.get(project_info_url)
            if project_response.status_code == 200:
                project_data = project_response.json()
                if project_data.get('empty_repo', False):
                    logger.warning(f"项目 {project_id} 是空仓库，没有提交记录")
                    return []
            else:
                logger.warning(f"无法获取项目信息，状态码: {project_response.status_code}")
        except Exception as e:
            logger.warning(f"检查项目信息时出错: {e}")

        try:
            # 添加请求间隔以避免API频率限制
            time.sleep(0.1)  # 每次请求间隔1秒

            # 重试机制：最多重试3次
            max_retries = 3
            for attempt in range(max_retries + 1):
                try:
                    response = self.session.get(url, params=params)
                    response.raise_for_status()
                    return response.json()
                except requests.exceptions.HTTPError as e:
                    if response.status_code == 429:  # Rate limit
                        wait_time = 2 ** attempt
                        logger.warning(f"API频率限制，等待 {wait_time} 秒后重试...")
                        time.sleep(wait_time)
                        continue
                    elif response.status_code == 404:
                        logger.error(f"项目 {project_id} 不存在或无权限访问，或者是空仓库")
                        logger.error(f"完整URL: {response.url}")
                        logger.error(f"响应内容: {response.text}")
                        return []
                    else:
                        logger.error(f"HTTP错误 {response.status_code}: {e}")
                        logger.error(f"完整URL: {response.url}")
                        logger.error(f"响应内容: {response.text}")
                        return []
                except requests.exceptions.RequestException as e:
                    logger.error(f"请求异常: {e}")
                    if attempt < max_retries:
                        wait_time = 2 ** attempt
                        logger.info(f"等待 {wait_time} 秒后重试...")
                        time.sleep(wait_time)
                    else:
                        return []

        except Exception as e:
            logger.error(f"获取提交记录时发生未知错误: {e}")
            return []

    def get_all_commits(self, project_id: str, **kwargs) -> List[Dict]:
        """
        获取项目的所有提交记录（分页获取）
        
        Args:
            project_id: 项目ID或路径
            **kwargs: 其他查询参数
        
        Returns:
            所有提交记录列表
        """
        all_commits = []
        page = 1
        per_page = kwargs.get('per_page', 100)  # 每页最多100条

        while True:
            logger.info(f"正在获取第 {page} 页提交记录...")

            # 更新分页参数
            current_kwargs = kwargs.copy()
            current_kwargs.update({
                'page': page,
                'per_page': per_page
            })

            commits = self.get_repo_commits(project_id, **current_kwargs)

            if not commits:
                break

            all_commits.extend(commits)

            # 如果返回的提交数少于每页数量，说明已经是最后一页
            if len(commits) < per_page:
                break

            page += 1

            # 添加延迟避免API限制
            time.sleep(0.1)

        logger.info(f"总共获取到 {len(all_commits)} 条提交记录")
        return all_commits

    def get_single_commit(self, project_id: str, commit_sha: str) -> Dict:
        """
        获取单个提交的详细信息，包括统计数据
        
        Args:
            project_id: 项目ID或路径
            commit_sha: 提交SHA
        
        Returns:
            提交详细信息，包含stats字段
        """
        try:
            # 对项目ID进行URL编码
            encoded_project_id = quote_plus(str(project_id))
            url = f"{self.base_url}/projects/{encoded_project_id}/repository/commits/{commit_sha}"

            response = self.session.get(url)

            if response.status_code == 200:
                commit_detail = response.json()
                logger.debug(f"获取提交 {commit_sha[:8]} 详细信息成功")
                return commit_detail
            else:
                logger.warning(f"获取提交 {commit_sha[:8]} 详细信息失败: HTTP {response.status_code}")
                return {}

        except Exception as e:
            logger.error(f"获取提交 {commit_sha[:8]} 详细信息时发生错误: {e}")
            return {}

    def get_user_by_email(self, email: str) -> Optional[Dict]:
        """
        通过邮箱获取GitLab用户信息
        
        Args:
            email: 用户邮箱
        
        Returns:
            用户信息字典，如果未找到则返回None
        """
        try:
            # 使用GitLab API搜索用户
            url = f"{self.base_url}/users"
            params = {
                'search': email,
                'per_page': 1
            }

            response = self.session.get(url, params=params)

            if response.status_code == 200:
                users = response.json()
                if users:
                    # 检查第一个用户的邮箱是否匹配
                    user = users[0]
                    if user.get('email') == email or user.get('public_email') == email:
                        logger.debug(f"找到用户: {user.get('username')} ({email})")
                        return user
                    else:
                        # 如果搜索结果的邮箱不匹配，尝试获取用户详细信息
                        user_detail_url = f"{self.base_url}/users/{user['id']}"
                        detail_response = self.session.get(user_detail_url)
                        if detail_response.status_code == 200:
                            user_detail = detail_response.json()
                            if user_detail.get('email') == email:
                                logger.debug(f"找到用户: {user_detail.get('username')} ({email})")
                                return user_detail

                logger.debug(f"未找到邮箱为 {email} 的用户")
                return None
            else:
                logger.warning(f"搜索用户失败: HTTP {response.status_code}")
                return None

        except Exception as e:
            logger.error(f"搜索用户 {email} 时发生错误: {e}")
            return None

    def get_project_branches(self, project_id: str) -> List[str]:
        """
        获取项目的分支列表
        
        Args:
            project_id: 项目ID或路径
        
        Returns:
            分支名称列表
        """
        # 对项目ID进行URL编码
        encoded_project_id = quote_plus(str(project_id))
        url = f"{self.base_url}/projects/{encoded_project_id}/repository/branches"

        try:
            response = self.session.get(url)
            response.raise_for_status()
            branches_data = response.json()
            return [branch['name'] for branch in branches_data]
        except Exception as e:
            logger.error(f"获取分支列表失败: {e}")
            return ['main', 'master']  # 返回默认分支

    def get_commit_statistics(self, project_id: str, **kwargs) -> Dict:
        """
        获取项目的提交统计信息
        
        Args:
            project_id: 项目ID或路径
            **kwargs: 其他查询参数
        
        Returns:
            提交统计信息
        """
        commits = self.get_all_commits(project_id, **kwargs)

        if not commits:
            return {
                'total_commits': 0,
                'contributors': [],
                'date_range': {},
                'commit_frequency': {}
            }

        # 统计信息
        contributors = {}
        commit_dates = []

        for commit in commits:
            # 提交者统计
            author_email = commit.get('author_email', 'unknown')
            author_name = commit.get('author_name', 'Unknown')

            if author_email not in contributors:
                contributors[author_email] = {
                    'name': author_name,
                    'email': author_email,
                    'commits': 0,
                    'first_commit': commit.get('created_at'),
                    'last_commit': commit.get('created_at')
                }

            contributors[author_email]['commits'] += 1

            # 更新最后提交时间
            commit_time = commit.get('created_at')
            if commit_time:
                if commit_time > contributors[author_email]['last_commit']:
                    contributors[author_email]['last_commit'] = commit_time
                if commit_time < contributors[author_email]['first_commit']:
                    contributors[author_email]['first_commit'] = commit_time

                commit_dates.append(commit_time)

        # 日期范围
        date_range = {}
        if commit_dates:
            date_range = {
                'earliest': min(commit_dates),
                'latest': max(commit_dates)
            }

        return {
            'total_commits': len(commits),
            'contributors': list(contributors.values()),
            'date_range': date_range,
            'commit_frequency': self._calculate_commit_frequency(commits)
        }

    def _calculate_commit_frequency(self, commits: List[Dict]) -> Dict:
        """
        计算提交频率统计
        
        Args:
            commits: 提交记录列表
        
        Returns:
            提交频率统计
        """
        daily_commits = {}
        hourly_commits = {}
        weekday_commits = {}

        for commit in commits:
            commit_time = commit.get('created_at')
            if not commit_time:
                continue

            try:
                dt = datetime.fromisoformat(commit_time.replace('Z', '+00:00'))

                # 按日统计
                date_key = dt.date().isoformat()
                daily_commits[date_key] = daily_commits.get(date_key, 0) + 1

                # 按小时统计
                hour_key = dt.hour
                hourly_commits[hour_key] = hourly_commits.get(hour_key, 0) + 1

                # 按星期统计
                weekday_key = dt.strftime('%A')
                weekday_commits[weekday_key] = weekday_commits.get(weekday_key, 0) + 1

            except Exception as e:
                logger.warning(f"解析提交时间失败: {commit_time}, 错误: {e}")
                continue

        return {
            'daily': daily_commits,
            'hourly': hourly_commits,
            'weekday': weekday_commits
        }

    def get_student_level_statistics(self, project_ids: List[str], progress_callback=None,
                                     include_line_stats: bool = True, **kwargs) -> List[Dict]:
        """
        获取多个项目的学生级别统计信息
        
        Args:
            project_ids: 项目ID列表
            progress_callback: 进度回调函数
            include_line_stats: 是否包含代码行数统计
            **kwargs: 其他查询参数
        
        Returns:
            学生统计信息列表
        """
        all_student_stats = {}
        total_projects = len(project_ids)

        for i, project_id in enumerate(project_ids):
            if progress_callback:
                progress_callback(i + 1, total_projects, f"正在分析项目: {project_id}")

            logger.info(f"正在分析项目 {project_id} ({i + 1}/{total_projects})")

            commits = self.get_all_commits(project_id, **kwargs)

            if not commits:
                logger.warning(f"项目 {project_id} 没有提交记录")
                continue

            # 缓存邮箱到用户名的映射，避免重复API调用
            email_to_username = {}

            # 统计每个学生的数据
            for commit_idx, commit in enumerate(commits):
                author_email = commit.get('author_email', 'unknown')
                author_name = commit.get('author_name', 'Unknown')
                commit_time = commit.get('created_at')

                # 尝试通过邮箱获取真实的GitLab用户名
                user_key = author_name  # 默认使用作者姓名
                if author_email and author_email != 'unknown':
                    if author_email not in email_to_username:
                        # 通过邮箱查找GitLab用户
                        gitlab_user = self.get_user_by_email(author_email)
                        if gitlab_user and gitlab_user.get('username'):
                            email_to_username[author_email] = gitlab_user['username']
                            user_key = gitlab_user['username']
                            logger.debug(f"邮箱 {author_email} 映射到用户名: {user_key}")
                        else:
                            # 如果找不到GitLab用户，使用邮箱作为标识
                            email_to_username[author_email] = author_email
                            user_key = author_email
                            logger.debug(f"未找到邮箱 {author_email} 对应的GitLab用户，使用邮箱作为标识")
                    else:
                        # 使用缓存的映射
                        user_key = email_to_username[author_email]

                if user_key not in all_student_stats:
                    all_student_stats[user_key] = {
                        'user_name': user_key,
                        'author_name': author_name,
                        'author_email': author_email,
                        'total_commits': 0,
                        'total_add_lines': 0,
                        'total_delete_lines': 0,
                        'total_code_lines': 0,
                        'repos_involved': set(),
                        'commit_dates': set(),
                        'last_active_time': None
                    }

                stats = all_student_stats[user_key]
                stats['total_commits'] += 1
                stats['repos_involved'].add(project_id)

                # 处理提交时间
                if commit_time:
                    try:
                        dt = datetime.fromisoformat(commit_time.replace('Z', '+00:00'))
                        commit_date = dt.date()
                        stats['commit_dates'].add(commit_date)

                        # 更新最后活跃时间
                        if not stats['last_active_time'] or dt > stats['last_active_time']:
                            stats['last_active_time'] = dt
                    except Exception as e:
                        logger.warning(f"解析提交时间失败: {commit_time}, 错误: {e}")

                # 获取提交的代码变更统计（可选）
                if include_line_stats:
                    # GitLab API在获取提交列表时不包含stats，需要单独获取每个提交的详细信息
                    # 为了避免API限制，我们批量处理并添加延迟
                    try:
                        # 显示详细进度
                        if (commit_idx + 1) % 10 == 0 or commit_idx == len(commits) - 1:
                            logger.info(f"项目 {project_id}: 正在处理提交 {commit_idx + 1}/{len(commits)}")

                        commit_detail = self.get_single_commit(project_id, commit['id'])
                        if commit_detail and 'stats' in commit_detail:
                            additions = commit_detail['stats'].get('additions', 0)
                            deletions = commit_detail['stats'].get('deletions', 0)
                            stats['total_add_lines'] += additions
                            stats['total_delete_lines'] += deletions
                            logger.debug(f"提交 {commit['id'][:8]}: +{additions} -{deletions}")
                        else:
                            # 如果无法获取详细统计，使用默认值0
                            logger.warning(f"无法获取提交 {commit['id'][:8]} 的统计信息")

                        # 添加延迟避免API限制
                        time.sleep(0.1)

                    except Exception as e:
                        logger.warning(f"获取提交 {commit['id'][:8]} 统计信息失败: {e}")
                else:
                    # 如果不需要详细行数统计，设置为0或跳过
                    # 这样可以大大提高处理速度
                    pass

        # 转换为最终格式
        result = []
        for email, stats in all_student_stats.items():
            stats['repos_involved'] = len(stats['repos_involved'])
            stats['active_days'] = len(stats['commit_dates'])

            # 格式化最后活跃时间
            if stats['last_active_time']:
                stats['last_active_time'] = stats['last_active_time'].strftime('%Y-%m-%d')
            else:
                stats['last_active_time'] = '未知'

            # 计算有效代码行数
            stats['total_code_lines'] = stats['total_add_lines'] - stats['total_delete_lines']

            # 移除临时字段
            del stats['commit_dates']

            result.append(stats)

        # 按总提交数排序
        result.sort(key=lambda x: x['total_commits'], reverse=True)

        return result

    def get_student_level_statistics_by_projects(self, project_ids: List[str], progress_callback=None,
                                                 include_line_stats: bool = True, **kwargs) -> List[Dict]:
        """
        获取多个项目的学生级别统计信息（别名方法，保持与GitCode兼容）
        
        Args:
            project_ids: 项目ID列表
            progress_callback: 进度回调函数
            include_line_stats: 是否包含代码行数统计
            **kwargs: 其他查询参数
        
        Returns:
            学生统计信息列表
        """
        return self.get_student_level_statistics(project_ids, progress_callback, include_line_stats, **kwargs)

    def commits_to_dataframe(self, commits: List[Dict]) -> pd.DataFrame:
        """
        将提交记录转换为DataFrame
        
        Args:
            commits: 提交记录列表
        
        Returns:
            包含提交信息的DataFrame
        """
        if not commits:
            return pd.DataFrame()

        processed_commits = []

        for commit in commits:
            try:
                commit_time = commit.get('created_at')
                dt = None
                if commit_time:
                    dt = datetime.fromisoformat(commit_time.replace('Z', '+00:00'))

                processed_commit = {
                    'id': commit.get('id'),
                    'short_id': commit.get('short_id'),
                    'title': commit.get('title', ''),
                    'message': commit.get('message', ''),
                    'author_name': commit.get('author_name', 'Unknown'),
                    'author_email': commit.get('author_email', 'unknown'),
                    'authored_date': commit.get('authored_date'),
                    'committer_name': commit.get('committer_name', 'Unknown'),
                    'committer_email': commit.get('committer_email', 'unknown'),
                    'committed_date': commit.get('committed_date'),
                    'created_at': commit_time,
                    'web_url': commit.get('web_url', ''),
                }

                if dt:
                    processed_commit.update({
                        'author_date': dt,
                        'year': dt.year,
                        'month': dt.month,
                        'day': dt.day,
                        'hour': dt.hour,
                        'weekday': dt.strftime('%A'),
                        'date': dt.date()
                    })

                processed_commits.append(processed_commit)

            except Exception as e:
                logger.warning(f"处理提交记录时出错: {e}")
                continue

        df = pd.DataFrame(processed_commits)

        # 转换日期列
        date_columns = ['authored_date', 'committed_date', 'created_at']
        for col in date_columns:
            if col in df.columns:
                df[col] = pd.to_datetime(df[col], errors='coerce')

        return df

    def analyze_commits(self, df: pd.DataFrame) -> Dict:
        """
        分析提交数据
        
        Args:
            df: 提交数据DataFrame
        
        Returns:
            分析结果字典
        """
        if df.empty:
            return {}

        analysis = {
            'total_commits': len(df),
            'unique_authors': df['author_email'].nunique() if 'author_email' in df.columns else 0,
            'date_range': {},
            'top_contributors': {},
            'activity_patterns': {}
        }

        # 日期范围分析
        if 'author_date' in df.columns:
            date_col = df['author_date'].dropna()
            if not date_col.empty:
                analysis['date_range'] = {
                    'start': date_col.min(),
                    'end': date_col.max(),
                    'days': (date_col.max() - date_col.min()).days
                }

        # 贡献者分析
        if 'author_email' in df.columns:
            author_commits = df['author_email'].value_counts().head(10)
            analysis['top_contributors'] = author_commits.to_dict()

        return analysis

    def generate_report(self, project_id: str, **kwargs) -> pd.DataFrame:
        """
        生成项目提交报告
        
        Args:
            project_id: 项目ID或路径
            **kwargs: 其他查询参数
        
        Returns:
            提交数据DataFrame
        """
        logger.info(f"开始生成项目 {project_id} 的提交报告...")

        # 获取提交记录
        commits = self.get_all_commits(project_id, **kwargs)

        if not commits:
            logger.warning(f"项目 {project_id} 没有提交记录")
            return pd.DataFrame()

        # 转换为DataFrame
        df = self.commits_to_dataframe(commits)

        # 分析数据
        analysis = self.analyze_commits(df)

        logger.info(f"报告生成完成，共 {analysis.get('total_commits', 0)} 条提交记录")

        return df


def get_random_gitlab_token():
    """
    从环境变量中随机获取一个GitLab访问令牌
    支持多个令牌以避免API限制
    
    Returns:
        随机选择的访问令牌
    """
    tokens_str = os.getenv('GITLAB_ACCESS_TOKEN', '')
    if not tokens_str:
        raise ValueError("请设置环境变量 GITLAB_ACCESS_TOKEN")

    tokens = [token.strip() for token in tokens_str.split(',') if token.strip()]
    if not tokens:
        raise ValueError("GITLAB_ACCESS_TOKEN 环境变量为空")

    return random.choice(tokens)


def main():
    """
    主函数 - 示例用法
    """
    try:
        # 从环境变量获取配置
        gitlab_url = os.getenv('GITLAB_URL', 'http://gitlab.waterism.com:8888')
        access_token = get_random_gitlab_token()

        # 创建分析器
        analyzer = GitLabCommitsAnalyzer(access_token, gitlab_url)

        # 示例：分析特定项目
        project_id = "1"  # 替换为实际的项目ID

        logger.info(f"开始分析GitLab项目: {project_id}")

        # 生成报告
        df = analyzer.generate_report(project_id)

        if not df.empty:
            logger.info(f"成功获取 {len(df)} 条提交记录")
            logger.info(df.head())
        else:
            logger.warning("没有获取到提交记录")

    except Exception as e:
        logger.error(f"程序执行失败: {e}")


if __name__ == "__main__":
    main()
    logger.info("请先运行 gitlab_repos_fetcher.py 获取项目列表")
    logger.info("然后修改 project_id 变量为要分析的具体项目ID")
